package com.gitee.frank.minispring.context;

import com.gitee.frank.minispring.beans.Bean;
import com.gitee.frank.minispring.config.Property;
import com.gitee.frank.minispring.config.parse.ConfigManager;
import com.gitee.frank.minispring.utils.BeanFactoryUtil;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Description:
 *
 * @author: Frank
 * @date: 2018/3/13
 */
public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String, Bean> config;
    private Map<String, Object> context = new HashMap<>();

    public ClassPathXmlApplicationContext(String path) {
        config = ConfigManager.getConfig(path);
        if (config != null) {
            for (Map.Entry<String, Bean> en : config.entrySet()) {
                String beanId = en.getKey();
                Bean bean = en.getValue();
                Object existBean = context.get(beanId);
                if (existBean == null && bean.getScope().equals("singleton")) {
                    existBean = createBean(bean);
                    context.put(beanId, existBean);
                }
            }
        }
    }

    /**
     * @param bean
     * @return
     */
    private Object createBean(Bean bean) {
        String className = bean.getClassName();
        Class clazz;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new ApplicationContextException("Class not found:" + className);
        }
        Object beanObj;
        try {
            beanObj = clazz.newInstance();
        } catch (Exception e) {
            throw new ApplicationContextException("Bean without parameterless constructor:" + className, e);
        }

        if (bean.getProperties() != null) {
            for (Property prop : bean.getProperties()) {
                String name = prop.getName();
                Method writeMethod = BeanFactoryUtil.getWriteMethod(beanObj, name);

                String value;
                if ((value = prop.getValue()) != null) {
                    Map<String, String[]> params = new HashMap<>();
                    params.put(name, new String[]{value});
                    try {
                        BeanUtils.populate(beanObj, params);
                    } catch (IllegalAccessException e) {
                        throw new ApplicationContextException("属性不可读，属性注入失败", e);
                    } catch (InvocationTargetException e) {
                        throw new ApplicationContextException("属性注入失败", e);
                    }
                }

                String ref;
                if ((ref = prop.getRef()) != null) {
                    Object refBean = context.get(ref);
                    if (refBean == null) {
                        refBean = createBean(config.get(prop.getRef()));
                        if (config.get(prop.getRef()).equals("singleton")) {
                            context.put(prop.getRef(), refBean);
                        }
                        try {
                            writeMethod.invoke(beanObj, refBean);
                        } catch (IllegalAccessException e) {
                            throw new ApplicationContextException("写属性权限异常，属性注入失败:" + name, e);
                        } catch (IllegalArgumentException e) {
                            throw new ApplicationContextException("非法参数，属性注入失败:" + name, e);
                        } catch (InvocationTargetException e) {
                            throw new ApplicationContextException("属性注入异常:" + name, e);
                        }
                    }
                }

            }
        }
        return beanObj;
    }

    /**
     * @param ID
     * @return
     */
    @Override
    public Object getBean(String ID) {
        Object obj = context.get(ID);
        if (obj == null) {
            Bean bean;
            if ((bean = config.get(ID)) == null) {
                throw new ApplicationContextException("未配置的Bean:" + ID);
            }
            obj = createBean(bean);
        }
        return obj;
    }
}